package com.rw.coco.handler.message.reply.handler.command.bean;

import com.rw.coco.api.data.model.CharacterPropertyModel;
import com.rw.coco.handler.message.reply.handler.command.exception.global.NumberOverflowException;
import com.rw.coco.handler.message.reply.handler.command.exception.ra.CommandEmptyException;
import com.rw.coco.handler.message.reply.handler.command.exception.ra.CommandTailException;
import com.rw.coco.handler.message.reply.handler.command.exception.ra.PropNotFoundException;
import com.rw.coco.handler.message.reply.handler.command.exception.ra.PropNumOverflow;
import com.rw.coco.handler.message.reply.handler.command.exception.ra.TooManyBonusesException;
import com.rw.coco.handler.message.reply.handler.command.exception.ra.TooManyCountException;
import com.rw.coco.service.CharacterPropertyService;
import com.rw.coco.util.DiceRule;
import com.rw.coco.util.config.AnotherNameConfig;
import com.rw.tool.util.string.StringScanner;
import com.rw.tool.util.string.StringUtil;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;

import java.util.Optional;
import java.util.Set;
import java.util.regex.Pattern;

/**
 * 检定规则类
 */
@Getter
@Setter
@AllArgsConstructor
@ToString
public class RARule {
    // 代表多重检定的正则表达式
    private static final Pattern COUNT_PATTERN = Pattern.compile("\\d+#", Pattern.CASE_INSENSITIVE);
    // 代表奖励，惩罚骰子的正则表达式
    private static final Pattern BONUSES_PATTERN = Pattern.compile("[bp]\\d+", Pattern.CASE_INSENSITIVE);
    // 属性名的正则表达式
    private static final Pattern PROP_PATTERN = Pattern.compile("[^\\d+\\-]+", Pattern.CASE_INSENSITIVE);
    // 属性修正的正则表达式
    private static final Pattern SIGN_PATTERN = Pattern.compile("[+\\-]\\d+", Pattern.CASE_INSENSITIVE);

    // 多重检定最大限制
    private static final int MAX_COUNT = 10;
    // 奖励/惩罚骰子最大限制
    private static final int MAX_BONUSES = 9;

    // 属性名
    private String name;
    // 属性值
    private int value;
    // 检定次数
    private int count;
    // 规则
    private DiceRule diceRule;
    // 奖励/惩罚骰子
    private int bonusesDice;

    private RARule(DiceRule diceRule) {
        // 设置默认值
        this.name = StringUtil.EMPTY;
        this.value = 0;
        this.count = 1;
        this.diceRule = diceRule;
        this.bonusesDice = 0;
    }

    /**
     * 把指令解析为RARule对象
     *
     * @param rawCommand 原指令
     * @param diceRule   指定ROLL点规则
     * @return RARule对象
     */
    public static RARule parse(String rawCommand, DiceRule diceRule,
                               Set<CharacterPropertyModel> allProp, AnotherNameConfig anotherNameConfig) {
        StringScanner sc = new StringScanner(rawCommand);
        RARule raRule = new RARule(diceRule);

        // 设置多重检定，奖励/惩罚骰，属性名字
        setCountBonusesName(raRule, sc);

        if (sc.hasNextNumeric()) {
            // 如果有属性值指定，则设定属性值，完成规则解析
            setValueFromSpecifying(raRule, sc);
        } else {
            // 如果没有属性值指定，则解析后续指令
            setValueWithoutSpecifying(raRule, sc, allProp, anotherNameConfig);
        }

        // 如果指令还有后续，则指令格式错误
        if (sc.hasNext()) {
            throw new CommandTailException(sc.getOriginalString(), sc.getIndex());
        }

        return raRule;
    }

    /**
     * 设置多重检定，奖励/惩罚骰，属性名字
     *
     * @param raRule 骰点规则
     * @param sc     字符串扫描器
     */
    private static void setCountBonusesName(RARule raRule, StringScanner sc) {
        // 判断有无多重检定指令
        if (sc.hasNext(COUNT_PATTERN)) {
            setCount(raRule, sc.next(COUNT_PATTERN));
        }

        // 判断有无奖励/惩罚骰子
        if (sc.hasNext(BONUSES_PATTERN)) {
            setBonuses(raRule, sc.next(BONUSES_PATTERN));
        }

        // 如果没有属性名，则抛出异常
        if (!sc.hasNext(PROP_PATTERN)) {
            throw new CommandEmptyException();
        }

        // 设置解析出来的属性名
        String propName = sc.next(PROP_PATTERN);
        raRule.setName(propName);
    }

    /**
     * 指定了属性值之后，设置属性值
     *
     * @param raRule 骰点规则
     * @param sc     字符串扫描器
     */
    private static void setValueFromSpecifying(RARule raRule, StringScanner sc) {
        int value = sc.nextInteger().intValue();
        if (value > CocoProp.MAX_VALUE) {
            throw new PropNumOverflow(CocoProp.MAX_VALUE, value);
        }
        raRule.setValue(value);
    }

    /**
     * 没有指定属性值，解析后续指令并设置属性值
     *
     * @param raRule 骰点规则
     * @param sc     字符串扫描器
     */
    private static void setValueWithoutSpecifying(RARule raRule, StringScanner sc,
                                                  Set<CharacterPropertyModel> allProp,
                                                  AnotherNameConfig anotherNameConfig) {
        // 如果后续指令不符合正则表达式，则指令错误
        if (!sc.hasNext(SIGN_PATTERN) && sc.hasNext()) {
            throw new CommandTailException(sc.getOriginalString(), sc.getIndex());
        }

        // 尝试获取属性值
        String propName = anotherNameConfig.getMainName(raRule.getName());
        Optional<CharacterPropertyModel> propOptional = CharacterPropertyService.getPropFromSet(allProp, propName);
        // 如果属性值未找到，则抛出异常
        if (!propOptional.isPresent()) {
            throw new PropNotFoundException(propName);
        }

        // 获取属性值
        int value = propOptional.get().getPropertiesValue();

        // 属性值修正
        if (sc.hasNext(SIGN_PATTERN)) {
            value += sc.nextInteger().intValue();
        }

        raRule.setValue(value);
    }

    /**
     * 设置检定次数
     *
     * @param raRule  检定规则
     * @param command 命令
     */
    private static void setCount(RARule raRule, String command) {
        // 去掉指令最后的 # 号，解析为数字，并设置
        String countStr = command.substring(0, command.length() - 1);
        int count;
        try {
            count = Integer.parseInt(countStr);
        } catch (NumberFormatException exception) {
            throw new NumberOverflowException();
        }

        // 次数太多，抛出异常
        if (count > MAX_COUNT) {
            throw new TooManyCountException(MAX_COUNT, count);
        }

        raRule.setCount(count);
    }

    /**
     * 设置奖励次数
     *
     * @param raRule  检定规则
     * @param command 命令
     */
    private static void setBonuses(RARule raRule, String command) {
        // 判断是否是奖励骰
        String bonusesStr = command.substring(0, 1);
        boolean bonuses = bonusesStr.equalsIgnoreCase("b");

        // 获取奖励骰子数
        String bonusesCountStr = command.substring(1);
        int bonusesCount;
        try {
            bonusesCount = Integer.parseInt(bonusesCountStr);
        } catch (NumberFormatException exception) {
            throw new NumberOverflowException();
        }

        // 次数太多，抛出异常
        if (bonusesCount > MAX_BONUSES) {
            throw new TooManyBonusesException(MAX_BONUSES, bonusesCount);
        }

        // 如果为惩罚骰，则设置为负
        if (!bonuses) {
            bonusesCount = -bonusesCount;
        }

        raRule.setBonusesDice(bonusesCount);
    }
}
